package com.yvon.boot.log.aspect;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.URLUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.yvon.boot.log.annotation.SysLog;
import com.yvon.boot.log.event.LogEvent;
import com.yvon.boot.log.model.SystemLog;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;

/**
 * 日志拦截器
 *
 * @author : Yvon
 * @since : 2022-05-12
 */
@Slf4j
@Aspect
@Component
public class LogAspect {

    @Autowired
    private final ApplicationContext applicationContext;

    private static final ThreadLocal<SystemLog> THREAD_LOCAL_LOG = new TransmittableThreadLocal<>();

    public LogAspect(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }


    @Pointcut("execution(public * com.yvon.*.service.*.controller.*.*(..)) || @annotation(com.yvon.boot.log.annotation.SysLog)")
    public void sysLogAspect() {
    }

    /**
     * 前置
     * 记录信息：请求参数、请求地址、请求方法、请求IP、标题、操作说明、开始时间、操作人
     *
     */
    @Before("sysLogAspect()")
    public void doBefore(JoinPoint joinPoint) {
        this.tryCatch((val) -> {
            // 是否存在SysLog注解，有则使用SysLog的内容进行记录
            SysLog sysLog = getSysLogAnnotation(joinPoint);
            if (check(joinPoint, sysLog)) {
                String operation = null;;
                if (Objects.nonNull(sysLog)) {
                    operation = sysLog.value();
                } else {
                    // 是否存在ApiOperation和Api注解，有则使用ApiOperation的内容进行记录
                    ApiOperation apiOperation = getApiOperationAnnotation(joinPoint);
                    if (Objects.nonNull(apiOperation)) {
                        operation = apiOperation.value();
                    }
                }
                SystemLog systemLog = buildSystemLog(joinPoint, operation);
                THREAD_LOCAL_LOG.set(systemLog);
            }
        });
    }

    @AfterReturning(returning = "ret", pointcut = "sysLogAspect()")
    public void doAfterReturning(JoinPoint joinPoint, Object ret) {
        this.tryCatch((val) -> {
            if (Objects.nonNull(ret)) {
                if (check(joinPoint, getSysLogAnnotation(joinPoint))) {
                    SystemLog systemLog = get();
                    systemLog.setType("success");
                    publishEvent(systemLog);
                }
            }
        });
    }

    @AfterThrowing(throwing = "e", pointcut = "sysLogAspect()")
    public void doAfterThrowable(JoinPoint joinPoint, Throwable e) {
        this.tryCatch((val) -> {
            SysLog sysLog = getSysLogAnnotation(joinPoint);
            if (check(joinPoint, sysLog)) {
                SystemLog systemLog = get();
                systemLog.setType("error");
                if (Objects.nonNull(sysLog)) {
                    systemLog.setException(sysLog.exception());
                }else {
                    systemLog.setException(ExceptionUtil.stacktraceToString(e, 65535));
                }
                publishEvent(systemLog);
            }
        });
    }


    private void tryCatch(Consumer<String> consumer) {
        try {
            consumer.accept("");
        } catch (Exception e) {
            log.warn("记录操作日志异常", e);
            THREAD_LOCAL_LOG.remove();
        }
    }

    /**
     * 获取方法上的SysLog注解
     *
     * @param point 切点
     * @return SysLog
     */
    public SysLog getSysLogAnnotation(JoinPoint point) {
        try {
            SysLog annotation = null;
            if (point.getSignature() instanceof MethodSignature) {
                Method method = ((MethodSignature)point.getSignature()).getMethod();
                if (method != null) {
                    annotation = method.getAnnotation(SysLog.class);
                }
            }

            return annotation;
        } catch (Exception var3) {
            log.warn("获取 {}.{} 的 @SysLog 注解失败", point.getSignature().getDeclaringTypeName(), point.getSignature().getName(), var3);
            return null;
        }
    }

    /**
     * 获取方法上的ApiOperation注解（来自swagger）
     *
     * @param point 切点
     * @return ApiOperation
     */
    public ApiOperation getApiOperationAnnotation(JoinPoint point) {
        try {
            ApiOperation annotation = null;
            if (point.getSignature() instanceof MethodSignature) {
                Method method = ((MethodSignature)point.getSignature()).getMethod();
                if (method != null) {
                    annotation = method.getAnnotation(ApiOperation.class);
                }
            }
            return annotation;
        } catch (Exception var3) {
            log.warn("获取 {}.{} 的 @ApiOperation 注解失败", point.getSignature().getDeclaringTypeName(), point.getSignature().getName(), var3);
            return null;
        }
    }


    private SystemLog buildSystemLog(JoinPoint joinPoint, String operation) {
        HttpServletRequest request = ((ServletRequestAttributes)Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        SystemLog systemLog = this.get();
        setUser(request, systemLog);
        systemLog.setOperation(operation);
        systemLog.setHttpMethod(request.getMethod());
        systemLog.setIp(getHttpServletRequestIpAddress(request));
        Object[] args = joinPoint.getArgs();
        systemLog.setParam(JSON.toJSONString(getArgs(args, request)));
        systemLog.setUrl(request.getRequestURI());
        systemLog.setClassPath(joinPoint.getTarget().getClass().getName());
        systemLog.setStartTime(LocalDateTime.now());
        Api api = joinPoint.getTarget().getClass().getAnnotation(Api.class);
        if (api != null) {
            String[] tags = api.tags();
            if (ArrayUtil.isNotEmpty(tags)) {
                systemLog.setTitle(tags[0]);
            }
        }
        return systemLog;
    }

    private void setUser(HttpServletRequest request , SystemLog systemLog) {
        String loginUserStr = request.getHeader("login_user");
        if (StringUtils.hasText(loginUserStr)) {
            JSONObject jsonObject = JSONObject.parseObject(URLUtil.decode(loginUserStr));
            systemLog.setUsername((String) jsonObject.get("username"));
        }
    }

    private SystemLog get() {
        SystemLog sysLog = THREAD_LOCAL_LOG.get();
        return sysLog == null ? new SystemLog() : sysLog;
    }

    public static String getHttpServletRequestIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        if (ip.contains(",")) {
            ip = ip.split(",")[0];
        }
        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
    }

    /**
     * 获取请求参数
     */
    private String getArgs(Object[] args, HttpServletRequest request) {
        String strArgs = "";

        try {
            if (!request.getContentType().contains("multipart/form-data")) {
                strArgs = JSONObject.toJSONString(args);
            }
        } catch (Exception e) {
            try {
                strArgs = Arrays.toString(args);
            } catch (Exception ex) {
                log.warn("解析参数异常", ex);
            }
        }
        return strArgs;
    }

    private void publishEvent(SystemLog sysLog) {
        sysLog.setFinishTime(LocalDateTime.now());
        sysLog.setConsumingTime(sysLog.getStartTime().until(sysLog.getFinishTime(), ChronoUnit.MILLIS));
        applicationContext.publishEvent(new LogEvent(sysLog));
        THREAD_LOCAL_LOG.remove();
    }

    public Map<String, Object> objectToMap(Object obj) throws IllegalAccessException {
        Map<String, Object> map = new HashMap<String,Object>();
        Class<?> clazz = obj.getClass();
        for (Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            map.put(field.getName(), field.get(obj));
        }
        return map;
    }

    private boolean check(JoinPoint joinPoint, SysLog sysLog) {
        if (Objects.nonNull(sysLog)) {
            if (sysLog.enable()) {
                return false;
            }
        }
        SysLog targetClass = joinPoint.getTarget().getClass().getAnnotation(SysLog.class);
        if (targetClass == null) {
            return true;
        }
        return targetClass.enable();
    }


}
